with release 32.0.0 md5 custom checksum is removed from bulkupload
authorMatthieu Gallien <matthieu.gallien@nextcloud.com>
Mon, 31 Mar 2025 13:07:36 +0000 (15:07 +0200)
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>
Thu, 10 Apr 2025 09:31:04 +0000 (09:31 +0000)
makes our fake server require an md5 custom checksum for bulk upload
until version 32.0.0

starting with 32.0.0 this custom checksum must be empty

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
test/syncenginetestutils.cpp
test/syncenginetestutils.h
test/testsyncengine.cpp

index e956716a847386a5f177a11257a62bdfa0e6c386..e4a5d12a7c6827b23934918e6ed9febaee7ac692 100644 (file)
@@ -523,18 +523,19 @@ void FakePutReply::abort()
     emit finished();
 }
 
-FakePutMultiFileReply::FakePutMultiFileReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QString &contentType, const QByteArray &putPayload, QObject *parent)
+FakePutMultiFileReply::FakePutMultiFileReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QString &contentType, const QByteArray &putPayload, const QString &serverVersion, QObject *parent)
     : FakeReply { parent }
+    , _serverVersion(serverVersion)
 {
     setRequest(request);
     setUrl(request.url());
     setOperation(op);
     open(QIODevice::ReadOnly);
-    _allFileInfo = performMultiPart(remoteRootFileInfo, request, putPayload, contentType);
+    _allFileInfo = performMultiPart(remoteRootFileInfo, request, putPayload, contentType, _serverVersion);
     QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
 }
 
-QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload, const QString &contentType)
+QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload, const QString &contentType, const QString &serverVersion)
 {
     Q_UNUSED(request)
     QVector<FileInfo *> result;
@@ -560,7 +561,19 @@ QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRoot
         const auto standardChecksum = allHeaders[QStringLiteral("oc-checksum")];
         Q_ASSERT(!fileName.isEmpty());
         Q_ASSERT(modtime > 0);
-        Q_ASSERT(!expectedMd5Checksum.isEmpty());
+
+        auto components = serverVersion.split('.');
+        const auto serverIntVersion = OCC::Account::makeServerVersion(components.value(0).toInt(),
+                                                                      components.value(1).toInt(),
+                                                                      components.value(2).toInt());
+
+        const auto md5ChecksumMandatory = serverIntVersion < OCC::Account::makeServerVersion(32, 0, 0);
+
+        if (md5ChecksumMandatory) {
+            Q_ASSERT(!expectedMd5Checksum.isEmpty());
+        } else {
+            Q_ASSERT(expectedMd5Checksum.isEmpty());
+        }
         Q_ASSERT(!standardChecksum.isEmpty());
 
         const auto standardChecksumComponents = standardChecksum.split(':');
@@ -579,12 +592,15 @@ QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRoot
             Q_ASSERT(false);
         }
 
-        QCryptographicHash md5SumAlgorithm{QCryptographicHash::Algorithm::Md5};
-        QCryptographicHash standardSumAlgorithm{standardHashAlgorithm};
+        if (md5ChecksumMandatory) {
+            QCryptographicHash md5SumAlgorithm{QCryptographicHash::Algorithm::Md5};
+
+            md5SumAlgorithm.addData(onePartBody.toLatin1());
+            const auto computedMd5Checksum = md5SumAlgorithm.result().toHex();
+            Q_ASSERT(expectedMd5Checksum == computedMd5Checksum);
+        }
 
-        md5SumAlgorithm.addData(onePartBody.toLatin1());
-        const auto computedMd5Checksum = md5SumAlgorithm.result().toHex();
-        Q_ASSERT(expectedMd5Checksum == computedMd5Checksum);
+        QCryptographicHash standardSumAlgorithm{standardHashAlgorithm};
 
         standardSumAlgorithm.addData(onePartBody.toLatin1());
         const auto computedStandardChecksum = standardSumAlgorithm.result().toHex();
@@ -1143,7 +1159,7 @@ QNetworkReply *FakeQNAM::createRequest(QNetworkAccessManager::Operation op, cons
             reply = new FakeChunkMoveReply { info, _remoteRootFileInfo, op, newRequest, this };
         } else if (verb == QLatin1String("POST") || op == QNetworkAccessManager::PostOperation) {
             if (contentType.startsWith(QStringLiteral("multipart/related; boundary="))) {
-                reply = new FakePutMultiFileReply { info, op, newRequest, contentType, outgoingData->readAll(), this };
+                reply = new FakePutMultiFileReply { info, op, newRequest, contentType, outgoingData->readAll(), _serverVersion, this };
             }
         } else if (verb == QLatin1String("LOCK") || verb == QLatin1String("UNLOCK")) {
             reply = new FakeFileLockReply{info, op, newRequest, this};
@@ -1168,6 +1184,11 @@ QNetworkReply * FakeQNAM::overrideReplyWithError(QString fileName, QNetworkAcces
     return reply;
 }
 
+void FakeQNAM::setServerVersion(const QString &version)
+{
+    _serverVersion = version;
+}
+
 FakeFolder::FakeFolder(const FileInfo &fileTemplate, const OCC::Optional<FileInfo> &localFileInfo, const QString &remotePath)
     : _localModifier(_tempDir.path())
 {
@@ -1189,7 +1210,8 @@ FakeFolder::FakeFolder(const FileInfo &fileTemplate, const OCC::Optional<FileInf
     _account->setUrl(QUrl(QStringLiteral("http://admin:admin@localhost/owncloud")));
     _account->setCredentials(new FakeCredentials { _fakeQnam });
     _account->setDavDisplayName(QStringLiteral("fakename"));
-    _account->setServerVersion(QStringLiteral("10.0.0"));
+    _account->setServerVersion(_serverVersion);
+    _fakeQnam->setServerVersion(_serverVersion);
 
     _journalDb = std::make_unique<OCC::SyncJournalDb>(localPath() + QStringLiteral(".sync_test.db"));
     _syncEngine = std::make_unique<OCC::SyncEngine>(_account, localPath(), OCC::SyncOptions{}, remotePath, _journalDb.get());
@@ -1312,6 +1334,17 @@ void FakeFolder::enableEnforceWindowsFileNameCompatibility()
     });
 }
 
+void FakeFolder::setServerVersion(const QString &version)
+{
+    if (_serverVersion == version) {
+        return;
+    }
+
+    _serverVersion = version;
+    _account->setServerVersion(_serverVersion);
+    _fakeQnam->setServerVersion(_serverVersion);
+}
+
 FileInfo FakeFolder::currentLocalState()
 {
     QDir rootDir { _tempDir.path() };
index 817a138a58a3fadad9d53a9b423a13c2f026e730..44ed5edda7e3df2eb8e8a5384576e1c697a06f0e 100644 (file)
@@ -275,9 +275,9 @@ class FakePutMultiFileReply : public FakeReply
 {
     Q_OBJECT
 public:
-    FakePutMultiFileReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QString &contentType, const QByteArray &putPayload, QObject *parent);
+    FakePutMultiFileReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QString &contentType, const QByteArray &putPayload, const QString &serverVersion, QObject *parent);
 
-    static QVector<FileInfo *> performMultiPart(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload, const QString &contentType);
+    static QVector<FileInfo *> performMultiPart(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload, const QString &contentType, const QString &serverVersion);
 
     Q_INVOKABLE virtual void respond();
 
@@ -290,6 +290,8 @@ private:
     QVector<FileInfo *> _allFileInfo;
 
     QByteArray _payload;
+
+    QString _serverVersion;
 };
 
 class FakeMkcolReply : public FakeReply
@@ -501,6 +503,8 @@ private:
     // monitor requests and optionally provide custom replies
     Override _override;
 
+    QString _serverVersion = QStringLiteral("10.0.0");
+
 public:
     FakeQNAM(FileInfo initialRoot);
     FileInfo &currentRemoteState() { return _remoteRootFileInfo; }
@@ -516,6 +520,8 @@ public:
 
     QNetworkReply *overrideReplyWithError(QString fileName, Operation op, QNetworkRequest newRequest);
 
+    void setServerVersion(const QString &version);
+
 protected:
     QNetworkReply *createRequest(Operation op, const QNetworkRequest &request,
         QIODevice *outgoingData = nullptr) override;
@@ -553,6 +559,7 @@ class FakeFolder
     OCC::AccountPtr _account;
     std::unique_ptr<OCC::SyncJournalDb> _journalDb;
     std::unique_ptr<OCC::SyncEngine> _syncEngine;
+    QString _serverVersion = QStringLiteral("10.0.0");
 
 public:
     FakeFolder(const FileInfo &fileTemplate, const OCC::Optional<FileInfo> &localFileInfo = {}, const QString &remotePath = {});
@@ -561,6 +568,8 @@ public:
 
     void enableEnforceWindowsFileNameCompatibility();
 
+    void setServerVersion(const QString &version);
+
     [[nodiscard]] OCC::AccountPtr account() const { return _account; }
     [[nodiscard]] OCC::SyncEngine &syncEngine() const { return *_syncEngine; }
     [[nodiscard]] OCC::SyncJournalDb &syncJournal() const { return *_journalDb; }
index d1d182c82b3f9150acaa7c39f1e54e4c9291faa1..ec603c17e81fe52184d3805b7febe8322f98d03d 100644 (file)
@@ -180,6 +180,38 @@ private slots:
         QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
     }
 
+    void testDirUploadWithDelayedAlgorithmWithNewChecksum() {
+        FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
+        fakeFolder.setServerVersion(QStringLiteral("32.0.0"));
+        fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"bulkupload", "1.0"} } } });
+
+        ItemCompletedSpy completeSpy(fakeFolder);
+        fakeFolder.localModifier().mkdir("Y");
+        fakeFolder.localModifier().insert("Y/d0");
+        fakeFolder.localModifier().mkdir("Z");
+        fakeFolder.localModifier().insert("Z/d0");
+        fakeFolder.localModifier().insert("A/a0");
+        fakeFolder.localModifier().insert("B/b0");
+        fakeFolder.localModifier().insert("r0");
+        fakeFolder.localModifier().insert("r1");
+        fakeFolder.syncOnce();
+        QVERIFY(itemDidCompleteSuccessfullyWithExpectedRank(completeSpy, "Y", 0));
+        QVERIFY(itemDidCompleteSuccessfullyWithExpectedRank(completeSpy, "Z", 1));
+        QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Y/d0"));
+        QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "Y/d0") > 1);
+        QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z/d0"));
+        QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "Z/d0") > 1);
+        QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a0"));
+        QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "A/a0") > 1);
+        QVERIFY(itemDidCompleteSuccessfully(completeSpy, "B/b0"));
+        QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "B/b0") > 1);
+        QVERIFY(itemDidCompleteSuccessfully(completeSpy, "r0"));
+        QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "r0") > 1);
+        QVERIFY(itemDidCompleteSuccessfully(completeSpy, "r1"));
+        QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "r1") > 1);
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+    }
+
     void testLocalDelete() {
         FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
         ItemCompletedSpy completeSpy(fakeFolder);